package org.groovymud.object.registry.persistence;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import groovy.lang.GroovyObjectSupport;
import org.apache.log4j.Logger;

import java.io.*;
import java.util.Arrays;
import java.util.List;

import static org.codehaus.groovy.runtime.InvokerHelper.asList;

/**
 * loads an object using xstream, thread safe: cannot load and save at the same time
 * with any instance
 *
 * @param <T>
 * @author corbym
 */
public class XStreamDataPersistence<T> extends MudDataPersistence<T> {
    public static final Logger logger = Logger.getLogger(XStreamDataPersistence.class);

    private List<Converter> converters;
    private XStream xStream;
    private final String baseDirectory;

    private String fileExtension = ".xml";

    private boolean convertersRegistered = false;

    public XStreamDataPersistence(XStream xStream, String baseDir, Converter... someConverters) {
        baseDirectory = baseDir;
        this.converters = asList(someConverters);
        this.xStream = xStream;
    }

    public XStreamDataPersistence(String baseDir, String ext) {
        baseDirectory = baseDir;
        fileExtension = ext;
    }

    /**
     * {@inheritDoc}
     * <p/>
     * This implementation loads the object from an xml document in the file system. The handle
     * represents a directory under the specified base directory.
     */
    public T loadObject(String fileName) throws DataPersistenceException {
        if (xStream == null) {
            throw new IllegalArgumentException("You must set the xstream instance before using this class.");
        }
        T object = null;
        File file = createFile(baseDirectory.trim() + File.separator + fileName);
        synchronized (this.getClass()) {
            if (file.exists()) {
                logger.info("loading player file");
                InputStream xmlIn;

                try {
                    xmlIn = createFileInputStream(file);
                } catch (FileNotFoundException e) {
                    throw new DataPersistenceException("file not found:" + file.getAbsolutePath(), e);
                }

                object = (T) deserializeObject(xmlIn);

            }
        }
        return object;
    }

    protected void registerConverters() {
        if (!convertersRegistered) {
            for (Converter converter : converters) {
                xStream.registerConverter(converter);
            }
            convertersRegistered = true;
        }

    }

    public void serialize(T object, OutputStream out) {
        registerConverters();
        xStream.toXML(object, out);
    }

    public T deserializeObject(InputStream xmlIn) {
        registerConverters();
        return (T) xStream.fromXML(xmlIn);
    }

    /**
     * {@inheritDoc}
     * <p/>
     * This implementation saves the object as an xml document to the file system. The handle
     * represents a directory under the specified base directory.
     */
    public void saveObject(String fileName, T object) throws DataPersistenceException {

        if (xStream == null) {
            throw new IllegalArgumentException("You must set the xstream instance before using this class.");
        }
        xStream.omitField(GroovyObjectSupport.class, "metaClass");
        xStream.setMode(XStream.XPATH_RELATIVE_REFERENCES);
        synchronized (this.getClass()) {
            FileOutputStream out;
            try {
                out = new FileOutputStream(createFile(baseDirectory.trim() + File.separator + fileName));
            } catch (FileNotFoundException e) {
                throw new DataPersistenceException("Cannot persist:" + fileName + " to " + baseDirectory, e);
            }
            serialize(object, out);
        }
    }

    public boolean deleteObject(String fileName) {
        File file = createFile(baseDirectory.trim() + File.separator + fileName);
        return file.delete();
    }

    @Override
    public void clean() {
        cleanRecursively(baseDirectory);
    }

    private void cleanRecursively(String baseDirectory) {
        File file = new File(baseDirectory.trim());
        final File[] filesArray = file.listFiles();
        if (filesArray != null && filesArray.length > 0) {
            List<File> files = Arrays.asList(filesArray);
            for (File inDirectory : files) {
                if (inDirectory.isFile()) {
                    logger.info("deleting file:" + inDirectory.toString());
                    inDirectory.delete();
                } else {
                    cleanRecursively(inDirectory.getPath());
                }
            }
        }
    }

    private InputStream createFileInputStream(File playerFile) throws FileNotFoundException {
        return new FileInputStream(playerFile);
    }

    private File createFile(String fileName) {
        return new File(fileName + (fileName.endsWith(fileExtension) ? "" : fileExtension));
    }

    public XStream getxStream() {
        return xStream;
    }

    public String getBaseDirectory() {
        return baseDirectory;
    }

    /**
     * @return the converters
     */
    public List<Converter> getConverters() {
        return converters;
    }

}
